/**
 * 
 */
package adnotatio.common.xml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This is a common wrapper for XML elements.
 * 
 * @author kotelnikov
 */
public abstract class XMLElement extends XMLNode {
    /**
     * This map contains attribute names (instances of the {@link XMLName}
     * class) and the corresponding string values.
     */
    private Map fAttributes;

    /**
     * The list of child nodes ({@link XMLNode} instances) for this element.
     */
    private List fChildren;

    /**
     * Initializes this object with the real XML node object
     * 
     * @param document the document owning this node
     * @param node the node to set
     */
    public XMLElement(XMLDocument document, Object node) {
        super(document, node);
    }

    /**
     * Appends a new node to the internal element.
     * 
     * @param element the XML element
     * @param node the XML node to append to the element
     */
    protected abstract void appendChild(Object element, Object node);

    /**
     * Appends a new children to this element
     * 
     * @param child a child XML node wrapper
     */
    public final void appendChild(XMLNode child) {
        appendChild(fNode, child.fNode);
        fChildren = null;
    }

    /**
     * Returns the name of the given attribute object
     * 
     * @param attr the attribute object for which the name should be returned
     * @return the name of the given attribute object
     */
    protected abstract XMLName getAttributeName(Object attr);

    /**
     * This method returns a set of attribute names ({@link XMLName})
     * associated with this element
     * 
     * @return a set of attribute names ({@link XMLName}) associated with this
     *         element
     */
    public final Set getAttributeNames() {
        Map attributes = getAttributesMap();
        return attributes.keySet();
    }

    /**
     * Returns an iterator over all attribute objects of this element
     * 
     * @param node the internal XML node
     * @return an iterator over all attribute objects of this element
     */
    protected abstract Iterator getAttributes(Object node);

    /**
     * Returns a map with attribute names ({@link XMLName} instances) and the
     * corresponding string values
     * 
     * @return the map containing all attributes
     */
    private Map getAttributesMap() {
        if (fAttributes == null) {
            fAttributes = new HashMap();
            for (Iterator iterator = getAttributes(fNode); iterator.hasNext();) {
                Object attr = iterator.next();
                XMLName name = getAttributeName(attr);
                String value = getAttributeValue(attr);
                fAttributes.put(name, value);
            }
        }
        Map attributes = fAttributes;
        return attributes;
    }

    /**
     * Returns the value corresponding to the given attribute object
     * 
     * @param attr the attribute object for which the value should be returned
     * @return the value corresponding to the given attribute object
     */
    protected abstract String getAttributeValue(Object attr);

    /**
     * Returns the string value corresponding to the attribute with the given
     * name
     * 
     * @param name the name of the attribute to return
     * @return the string value corresponding to the attribute with the given
     *         name
     */
    public final String getAttributeValue(XMLName name) {
        Map attributes = getAttributesMap();
        return (String) attributes.get(name);
    }

    /**
     * Returns the child node of this element from the specified position.
     * 
     * @param pos the position of the node to return
     * @return the child node of this element from the specified position.
     */
    public final XMLNode getChild(int pos) {
        List list = getChildren();
        if (pos < 0 || pos >= list.size())
            throw new IndexOutOfBoundsException();
        return (XMLNode) list.get(pos);
    }

    /**
     * Returns the number of children in this element
     * 
     * @return the number of children in this element
     */
    public final int getChildNumber() {
        List children = getChildren();
        return children.size();
    }

    /**
     * Returns a list of all children of this element
     * 
     * @return a list of all children of this element
     */
    private List getChildren() {
        if (fChildren == null) {
            fChildren = new ArrayList();
            for (Iterator iterator = getChildrenIterator(fNode); iterator
                .hasNext();) {
                Object node = iterator.next();
                XMLNode xmlNode = newNode(node);
                if (xmlNode != null) {
                    fChildren.add(xmlNode);
                }
            }
        }
        return fChildren;
    }

    /**
     * Returns an iterator over all native child elements
     * 
     * @param node the internal node of this XML element
     * @return an iterator over all native child elements
     */
    protected abstract Iterator getChildrenIterator(Object node);

    /**
     * Returns the name of this element
     * 
     * @return the name of this element
     */
    public final XMLName getNodeName() {
        XMLName name = getNodeName(fNode);
        return name;
    }

    /**
     * Returns the name of the given internal XML node.
     * 
     * @param node the internal XML node for which the corresponding
     *        {@link XMLName} should be returned
     * @return the name of the given internal XML node
     */
    protected abstract XMLName getNodeName(Object node);

    /**
     * Returns an node wrapper corresponding to the given internal XML node
     * 
     * @param node the internal XML node for which the corresponding wrapper
     *        should be returned
     * @return an node wrapper corresponding to the given internal XML node
     */
    protected abstract XMLNode newNode(Object node);

}
